import React, {ReactNode, RefObject} from 'react'
import _ from 'lodash'
import {Result} from 'antd';
import {VJson, VJsonComponentProp} from "./types";
import {createViewId, deepTraverObject, isReact, YvanEventDispatch} from "./utils/helper";
import * as Extend from './extend'
import {reaction} from "mobx"
import moment from 'moment'
import 'moment/locale/zh-cn'
import * as AntdIcon from '@ant-design/icons'

import zhCN from 'antd/es/locale/zh_CN'
import enUS from 'antd/es/locale/en_US'
import {observer} from 'mobx-react';
import {IReactionDisposer} from "mobx/lib/internal";
import {Scope} from "./scope";
import Form from "./renderers/form/form";

moment.locale('zh-cn')

export {
    zhCN, enUS, moment
}

/*===================================================================================================
 * 注册组件(RegisteComponent) 相关的操作
 =================================================================================================== */

// 注册选项
export interface RegisterComponentOption {
    view: string
}

// 内部存放注册和组件缓存信息的类型
interface VJsonComponentIndexConfig {
    option: RegisterComponentOption

    // 实际渲染组件
    component: VJsonComponentBase<any, any>;

    //原始组件
    renderComponent: VJsonComponentBase<any, any>;
}


// 注册组件用的装饰器
export function VJsonComponent(option: RegisterComponentOption): any {
    return function <T extends VJsonComponentBase<any, any>>(component: T): T {
        const renderer = registerVJsonComponent(
            component,
            option
        );
        return renderer as T;
    }
}

// 生成组件属性的get/set方法用的装饰器
export function YvanPropertiesState(option: any) {
    const pState = _.cloneDeep(option.state);
    _.forOwn(pState, (value, key) => {
        Object.defineProperty(option, key, {
            get: function () {
                return option.state[key]
            },
            set: function (val) {
                option.setState({
                    [key]: val
                })
            }
        })
    });
}

export function registerVJsonComponent<T extends VJsonComponentBase<any, any>>(
    renderComponent: T,
    registeOption: RegisterComponentOption): T {

    // @ts-ignore
    const component = observer(renderComponent)
    const cfg: VJsonComponentIndexConfig = {
        renderComponent,
        // @ts-ignore
        component,
        option: registeOption
    }

    renderers.push(cfg)
    return cfg.component as T
}

// 渲染器缓存
const renderers: Array<VJsonComponentIndexConfig> = [];


/*===================================================================================================
 * 通用渲染( rendererCommon ) 操作
 =================================================================================================== */

export function renderItems<JSON, S>(me: VJsonComponentBase<JSON, S>, items: any[], extendDefault?: any): JSX.Element[] {

    const ret: JSX.Element[] = []
    _.forEach(items, (item) => {
        if (item)
            ret.push(render(me, item, extendDefault))
    })
    return ret
}

/**
 * 在 symbol 标记的位置渲染动态 vjson
 * @param scope 用户模块
 * @param symbol 位置标记
 * @param vjson 动态 vjson
 */
export function renderPlace<INP, REF>(scope: Scope<INP, REF>, symbol: Symbol, vjson: VJson) {

    // 加工 vjson
    deepTraverObject(vjson, (path, key, value) => {
        if (!key) {
            // key 没有值的，代表对象本身非 vjson 定义
            return
        }

        // on 开头的方法名，都是事件，如果事件是字符串类型，则转换为方法
        if (typeof value === 'string' && value && key.startsWith('on') && _.size(key) > 2) {
            // @ts-ignore
            const func = me[value]
            if (typeof func !== 'function') {
                throw new TypeError('无法找到名称为' + value + '的方法')
            }
            _.set(vjson, path, func)
        }
    })

    // 找到 symbol 位置, 并修改 vjson
    try {
        deepTraverObject(scope.vjson, (path, key, value) => {
            if (!key && value === symbol) {
                // 找到父容器访问路径
                const parentArrayPath = path.substr(0, path.lastIndexOf('['))

                const parentArray: any[] = _.get(scope.vjson, parentArrayPath)
                if (!parentArray) {
                    console.error(`renderPlace fail, path=${parentArrayPath} not exists`)
                    throw Symbol('jumpOut')
                }
                if (!_.isArray(parentArray)) {
                    console.error(`renderPlace fail, path=${parentArrayPath} not array`)
                    throw Symbol('jumpOut')
                }

                // 删除之前同样带 symbol 的元素
                _.remove(parentArray, (item) => item.symbol === symbol)

                // 找到 symbol 标记的位置
                const parentIdx = _.indexOf(parentArray, symbol)
                if (Extend.isDebugMode()) {
                    console.log(`renderPlace parentArrayPath=${parentArrayPath}, index=${parentIdx}`)
                }
                if (_.isArray(vjson)) {
                    parentArray.splice(parentIdx, 0, ...vjson.map(v => _.extend(v, {symbol})))
                } else {
                    parentArray.splice(parentIdx, 0, _.extend(vjson, {symbol}))
                }

                throw Symbol('jumpOut')
            }
        })
    } catch (e) {
    }
}

export function render<JSON, S>(me: VJsonComponentBase<JSON, S>, vjson: VJson, extendDefault?: any, paramOfRef?: RefObject<any>): JSX.Element {
    if (typeof vjson === 'function') {
        vjson = vjson()
    }
    if (isReact(vjson)) {
        //react 对象，直接输出
        return vjson as JSX.Element
    }
    if (typeof vjson === 'symbol') {
        // 只是一个位置标记
        // @ts-ignore
        return null
    }
    const {view} = vjson;
    if (!view) {
        console.error('无法推断 view 的定义', vjson)
        return loadRenderer(vjson, '无法推断 view 的定义')
    }

    let renderer: null | VJsonComponentIndexConfig = null

    renderers.some(item => {
        if (item.option.view === view) {
            renderer = item;
        }
        return false;
    });

    if (!renderer) {
        console.error('找不到对应的渲染器', vjson)
        return loadRenderer(vjson, `找不到对应的渲染器 ${view}`)
    }

    const Component = renderer!.component

    // defaultsDeep 越前(上)的，优先级越高，后面的会被忽略
    const $default = _.defaults({},
        extendDefault,
        vjson.$default,
        me.props.$default
    )

    const setupRef = (vjsonComInstance: any) => {

        if (typeof paramOfRef !== 'undefined') {
            // @ts-ignore
            paramOfRef.current = vjsonComInstance
        }

        const refProp = vjson['ref']
        if (!refProp) {
            return
        }

        if (vjsonComInstance) {
            me.props.$context.scopeInstance.refs[refProp] = vjsonComInstance

        } else {
            delete me.props.$context.scopeInstance.refs[refProp]
        }
    }

    // @ts-ignore
    return <Component
        key={_.uniqueId('_')}
        ref={setupRef}
        vjson={vjson}
        $default={$default}
        $context={me.props.$context}
    />
}

/*===================================================================================================
 * 渲染公共类( VJsonComponent/VJsonComponentBase/ ) 相关的操作
 =================================================================================================== */

export function renderIcon(icon?: ReactNode | string) {
    if (!icon) {
        return undefined
    }
    if (typeof icon === 'string') {
        if (icon.startsWith('antd')) {
            // @ts-ignore
            const Compoent = AntdIcon[icon.substr(5)]
            return <Compoent/>
        }
        return <i className={icon}/>
    }
    return icon
}

function loadRenderer(vjson: VJson, cause: string) {
    return (
        <Result
            status="error"
            title={cause}
        >
            <pre>
              <code>{JSON.stringify(vjson, null, 2)}</code>
            </pre>
        </Result>
    );
}

// export type VJsonComponent<JSON, P extends VJsonComponentProp<JSON>, S> = React.Component<P, S>
export abstract class VJsonComponentBase<JSON, S> extends React.Component<VJsonComponentProp<JSON>, S> {

    // @ts-ignore
    state!: S

    props!: VJsonComponentProp<JSON>

    /**
     * 属性观察的释放器
     */
    propertyWatchChain: { key: string, disposer: IReactionDisposer }[] = []

    protected constructor(props: any) {
        super(props);

        if (props.vjson && typeof props.vjson.view === 'string' && props.vjson.view) {
            if (typeof props.vjson.id !== 'string' || !props.vjson.id) {
                props.vjson.id = createViewId(props.vjson.view);
            }
        }
    }

    get view() {
        return this.props.vjson.view
    }

    get key() {
        return this.props.vjson.id
    }

    set visible(value: boolean) {
        // @ts-ignore
        this.props.vjson.visible = value
    }

    get visible() {
        return this.props.vjson.visible!
    }

    /**
     * 获取友好的组件名称 (bind 或 view)
     */
    toString() {
        // @ts-ignore
        if (this.formItemName) {
            // @ts-ignore
            return this.formItemName
        }

        if (this.props && this.props.vjson) {
            return this.props.vjson.bind ||
                this.props.vjson.text ||
                this.props.vjson.label ||
                this.props.vjson.view
        }

        return super.toString()
    }

    /**
     * 添加属性表达式计算
     */
    public addPropertyWatch(scope: any, vjson: VJson, propName: string) {
        const me = this
        let {$watch, $then} = vjson[propName]

        if (typeof $watch === 'string' && $watch) {
            $watch = scope[$watch]
            if (typeof $watch !== 'function') {
                throw new TypeError(`can't found method ${vjson[propName].$watch} on scope`)
            }
        }

        if (typeof $then === 'string' && $then) {
            $then = scope[$then]
            if (typeof $then !== 'function') {
                throw new TypeError(`can't found method ${vjson[propName].$then} on scope`)
            }
        }

        if (Extend.isDebugMode()) {
            console.log('PropertySetupWatch ', this.toString(), '.', propName)
        }
        const disposer = reaction(
            () => {
                const r = $watch.call(scope)
                if (Extend.isDebugMode()) {
                    console.log('PropertyWatch', this.toString(), '.', propName, '=', r, '  [$watch=', $watch)
                }
                return r
            },
            (newValue: any) => {
                if (typeof newValue === 'undefined') {
                    // 返回空值，代表不更新
                    if (Extend.isDebugMode()) {
                        console.log('PropertyWatch return undefined, ignore Effect.')
                    }
                    return
                }

                if (typeof $then === 'function') {

                    newValue = $then.call(scope, newValue)
                    if (Extend.isDebugMode()) {
                        console.log('PropertySetThen', this.toString(), '.', propName, '=', newValue,
                            '  [option=', {$watch, $then})
                    }


                } else {
                    if (Extend.isDebugMode()) {
                        console.log('PropertySet', this.toString(), '.', propName, '=', newValue,
                            '  [option=', {$watch, $then})
                    }
                }

                if (typeof newValue === "undefined") {
                    // 返回空值，代表不更新
                    if (Extend.isDebugMode()) {
                        console.log('PropertySetThen return null, ignore Effect.')
                    }
                    return
                }

                if (propName === 'value') {
                    // data 属性是设置到 state 的
                    //@ts-ignore
                    this['value'] = newValue

                } else {
                    vjson[propName] = newValue
                }

            },
            {
                fireImmediately: true
            }
        )

        this.propertyWatchChain.push({key: propName, disposer})
    }

    /**
     * 分析属性中是否带有 {'@watch':'...', '@watch':()=>any} 并自动添加观察
     * @param scope 用户业务
     * @param vjson 传入的 vjson
     */
    executePropertyExpression(scope: any, vjson: VJson) {
        _.forOwn(vjson, (value, prop) => {
            if (value && typeof value['$watch'] !== 'undefined') {
                this.addPropertyWatch(scope, vjson, prop)
            }
        })
    }

    componentWillMount() {
        if (Extend.isDebugMode()) {
            console.log('mount ' + this.toString())
        }

        // const ref = this.props.vjson['ref']
        // if (typeof ref === 'string') {
        //     this.props.$context.scopeInstance.refs[ref] = this
        // }

        this.executePropertyExpression(this.props.$context.scopeInstance, this.props.vjson)
        // @ts-ignore
        this.props.vjson.$getInstance = () => this
        YvanEventDispatch(this.props.vjson['onBeforeRender'], this);
    }

    componentDidMount() {
        YvanEventDispatch(this.props.vjson['onRender'], this);
    }

    componentWillUnmount() {
        delete this.props.vjson.$getInstance
        YvanEventDispatch(this.props.vjson['onDestory'], this);

        // const ref = this.props.vjson['ref']
        // if (typeof ref === 'string') {
        //     delete this.props.$context.scopeInstance.refs[ref]
        // }

        // 释放所有属性观察
        _.forEach(this.propertyWatchChain, f => f.disposer())

        this.propertyWatchChain = []
    }
}

/*===================================================================================================
 * 根级渲染( RootRenderer )
 =================================================================================================== */

export interface RootRendererProps extends VJsonComponentProp<VJson> {

}

export interface RootRendererState {
    $default: any
}

export class RootRenderer extends VJsonComponentBase<VJson, RootRendererState> {

    constructor(props: RootRendererProps) {
        super(props);

        const $default = this.props.$default

        this.state = {
            $default,
        }
    }

    componentWillMount() {
        // 这里调用的实际对象是 CreateScopeWrapper 制造的链式调用
        if (_.isFunction(this.props.$context.scopeInstance.onBeforeRender)) {
            this.props.$context.scopeInstance.onBeforeRender({
                vjson: this.props.vjson,
                cancel: false
            })
        }
    }

    componentDidMount() {
        // 用 defer 是因为 scope 会先于子组件先初始化
        if (Extend.isDebugMode()) {
            console.log('mount scope', this.props.$context.scopeInstance)
        }

        const me = this
        _.defer(() => {
            if (_.isFunction(me.props.$context.scopeInstance.onRender)) {
                me.props.$context.scopeInstance.onRender()
            }
        })
    }

    componentWillUnmount() {
        this.props.$context.scopeInstance._disposeExecute()

        if (_.isFunction(this.props.$context.scopeInstance.onDestory)) {
            this.props.$context.scopeInstance.onDestory()
        }
    }

    // componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    //     console.error(error);
    //     debugger
    // }

    render(): any {
        // 注意，这里的 this 指针，可能会被子组件使用
        const ret = render(this, this.props.vjson)
        return ret;
    }
}
